<?php

namespace App\Service\WaiMai;

use App\Library\Address\AutoNaviProvinceCityArea;
use App\Library\Rms\RmsShopApi;
use App\Models\WaiMai\WaiMaiAnalysisModel;
use App\Service\BaseService;
use App\Service\OpenShop\SiteLocation\GuanyuanDataService;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Str;
use Kuafu\File\FileUtil;
use Swoole\Coroutine\Channel;
use Swoole\Coroutine;
use function Swoole\Coroutine;

class WaiMaiAnalysisBuildService extends BaseService
{
    /**
     * 每次循环
     */
    const INSERT_EACH_LIMIT = 5000;

    /**
     * 入库用临时变量
     * @var array
     */
    private array $dbInsertData = [];
    private int   $dbInsertNum  = 0;


    /**
     * 准备三方外卖数据CSV
     * @return void
     */
    public function prepareThirdShopCsv()
    {
        ini_set('memory_limit', -1);

        WaiMaiAnalysisModel::query()->truncate();

        // 美团
        $url        = '/Users/July/Downloads/美团20221103.csv';
        $sourceType = 3;

        // 循环读取csv - 闭包
        $this->readCsv($url, function ($row, $line) use ($sourceType) {

            $shopName      = $line[4];
            $categoryName  = $line[3];
            $orderNum      = $line[5];
            $lng           = $line[21];
            $lat           = $line[20];
            $province      = $line[22];
            $city          = $line[23];
            $area          = $line[24];
            $addressPrefix = $province . '-' . $city . '-' . $area;

            if ($orderNum == '9999+') {
                $orderNum = 10000;
            }

            // 保存
            $this->prepareToSave($sourceType, $shopName, $categoryName, $orderNum, $lng, $lat, $addressPrefix);
        });
        $this->resetDbInsert();


        // 饿了么
        $url        = '/Users/July/Downloads/饿了么202303.csv';
        $sourceType = 4;
        $this->readCsv($url, function ($row, $line) use ($sourceType) {

            if (!isset($line[0]) || $line[0] == 'name') {
                return;
            }

            $shopName      = $line[0];
            $categoryName  = $line[15];
            $orderNum      = $line[5];
            $lng           = $line[17];
            $lat           = $line[16];
            $province      = $line[18];
            $city          = $line[19];
            $area          = $line[20];
            $addressPrefix = $province . '-' . $city . '-' . $area;

            $this->prepareToSave($sourceType, $shopName, $categoryName, $orderNum, $lng, $lat, $addressPrefix);
        });
        $this->resetDbInsert();

        dd(microtime(true) - LARAVEL_START);
    }

    /**
     * 准备三方外卖数据CSV - 协程版本
     * @return void
     */
    public function prepareThirdShopCsvCoroutine()
    {
        ini_set('memory_limit', -1);

//        $url = '/Users/July/Downloads/美团20221103.csv';
        $url        = '/home/www/美团20221103.csv';
        $sourceType = 3;

        Coroutine::set(['hook_flags' => SWOOLE_HOOK_ALL]);

        WaiMaiAnalysisModel::query()->truncate();

        Coroutine\run(function () use ($url, $sourceType) {
            $chan = new Channel(1024);

            // 读取CSV文件
            go(function () use ($url, $sourceType, $chan) {
                $this->readCsv($url, function ($row, $line) use ($sourceType, $chan) {
                    $shopName     = $line[4];
                    $categoryName = $line[3];
                    $orderNum     = $line[5];
                    $lng          = $line[21];
                    $lat          = $line[20];

                    if ($orderNum == '9999+') {
                        $orderNum = 10000;
                    }

                    $chan->push([
                        'sourceType'   => $sourceType,
                        'shopName'     => $shopName,
                        'categoryName' => $categoryName,
                        'orderNum'     => $orderNum,
                        'lng'          => $lng,
                        'lat'          => $lat,
                    ]);
                });

                $chan->push(false); // 发送结束信号
            });

            // 插入数据
            go(function () use ($chan) {
                while (true) {
                    $data = $chan->pop();
                    if ($data === false) {
                        break; // 结束
                    }

                    $sourceType   = $data['sourceType'];
                    $shopName     = $data['shopName'];
                    $categoryName = $data['categoryName'];
                    $orderNum     = $data['orderNum'];
                    $lng          = $data['lng'];
                    $lat          = $data['lat'];

                    $this->prepareToSave($sourceType, $shopName, $categoryName, $orderNum, $lng, $lat);
                }
            });
        });

        dd(microtime(true) - LARAVEL_START);
    }

    // 把所有数据写进DB
    // 美团/饿了么、品类、订单量、坐标、店铺名字
    private function prepareToSave(
        int $sourceType,
        string $shopName,
        string $categoryName,
        int $orderNum,
        string $lng,
        string $lat,
        string $addressPrefix,
        int $shopCode = 0
    ) {
        // 通过省市区名称获取省市区编码
        list($province, $city, $area) = explode('-', $addressPrefix);
        list($provinceCode, $cityCode, $areaCode) = AutoNaviProvinceCityArea::getInstance()
                                                                            ->getCodesByNames($province, $city, $area);

        $data = [
            'sourceType'    => $sourceType,
            'shopName'      => $shopName,
            'categoryName'  => $categoryName,
            'orderNum'      => $orderNum,
            'lng'           => $lng,
            'lat'           => $lat,
            'shopCode'      => $shopCode,
            'addressPrefix' => $addressPrefix,
            'provinceCode'  => $provinceCode,
            'cityCode'      => $cityCode,
            'areaCode'      => $areaCode,
            'categoryType'  => Str::contains($categoryName, ['炸串', '炸鸡', '夸父']) ? WaiMaiAnalysisModel::CATEGORY_TYPE_FRIED : WaiMaiAnalysisModel::CATEGORY_TYPE_DEFAULT,
        ];

        // mysql
        $this->dbInsertData[] = $data;
        $this->dbInsertNum++;

        if ($this->dbInsertNum == self::INSERT_EACH_LIMIT) {
            $this->resetDbInsert();
        }
    }

    private function resetDbInsert()
    {
        WaiMaiAnalysisModel::query()->insert($this->dbInsertData);

        $this->dbInsertNum  = 0;
        $this->dbInsertData = [];
    }

    /**
     * 更新夸父门店的信息 - 经纬度、美团订单数量、饿了么订单数量
     * @return void
     */
    public function updateKfShop()
    {
        $kfShops = [];

        try {
            $gyCommonShops = GuanyuanDataService::newShopList(0, [
                'name'        => '状态',
                'filterType'  => 'EQ',
                'filterValue' => [
                    '正常营业'
                ],
            ]);
        } catch (\Exception $exception) {
            $gyCommonShops = [];
        }

        foreach ($gyCommonShops as $shop) {

            $kfShops[(int)$shop['铺位号']] = [
                $shop['铺位号'],
                $shop['门店名称'],
                $shop['门店纬度'],
                $shop['门店经度'],
                0, // 美团订单数量
                0, // 饿了么订单数量
                $shop['门店所在省'] . '-' . $shop['门店所在市'] . '-' . $shop['区/县']
            ];
        }

        $shopCodes = array_column($gyCommonShops, '铺位号');
        $num       = 0;
        collect($shopCodes)->chunk(30)->each(function ($item) use (&$shopInfoArr, &$num, &$kfShops) {
            $shopCodes = [];
            foreach ($item as $value) {
                $shopCodes[] = str_pad($value, 4, '0', STR_PAD_LEFT);
            }
            try {
                GuanyuanDataService::setTimeOut(10);
                $guanyuanDataList = GuanyuanDataService::getShopBusinessInfoByCode($shopCodes);
            } catch (\Exception $exception) {
                BaseService::throwErr('获取门店详情超时,请稍后再试');
            }

            foreach ($guanyuanDataList as $guanyuanData) {

                $shopCode = $guanyuanData['code'] ?? 0;

                // 美团订单数量
                $kfShops[$shopCode][4] += (int)($guanyuanData['mtOrderNum'] ?? 0);

                // 饿了么订单数量
                $kfShops[$shopCode][5] += (int)($guanyuanData['elmOrderNum'] ?? 0);
            }
            $num += 1;

            dump('观远数据获取中... ' . $num);
        });

        dump("db insert start ...");

        DB::beginTransaction();

        WaiMaiAnalysisModel::query()->where('shopCode', '>', 0)->delete();

        // 重置id，否则id会越来越大
        DB::statement("ALTER TABLE waimai_analysis AUTO_INCREMENT = 1");

        // 把夸父门店也加入三方的外卖列表中，分类为夸父
        foreach ($kfShops as $shop) {

            if (!$shop[2] || !$shop[3]) {
                continue;
            }

            $this->prepareToSave(3, $shop[1], '夸父', $shop[4], $shop[3], $shop[2], $shop[6], (int)$shop[0]);
            $this->prepareToSave(4, $shop[1], '夸父', $shop[5], $shop[3], $shop[2], $shop[6], (int)$shop[0]);
        }

        $this->resetDbInsert();

        DB::commit();
    }

    /**
     * 生成所有省市的坐标文件
     * @return void
     */
    public function generateProvinceCityCoordinateOss()
    {
        foreach (AutoNaviProvinceCityArea::getInstance()->getProvinceCityArea() as $province) {
            $provinceCode = $province['adcode'];
            foreach ($province['districts'] as $city) {
                $cityCode = $city['adcode'];

                foreach ([RmsShopApi::SOURCE_TYPE_WM_MT, RmsShopApi::SOURCE_TYPE_WM_ELM] as $sourceType) {
                    $this->uploadCoordinateToOssAtom($provinceCode, $cityCode, $sourceType);
                }
            }
        }
    }

    /**
     * 根据入参将坐标上传到oss
     * @param int $province
     * @param int $city
     * @param int $sourceType
     * @return void
     */
    private function uploadCoordinateToOssAtom(int $province, int $city, int $sourceType)
    {
        $data = WaiMaiAnalysisModel::query()
                                   ->where('provinceCode', $province)
                                   ->where('cityCode', $city)
                                   ->where('sourceType', $sourceType)
                                   ->where('orderNum', '>', 0)
                                   ->select('lng as a', 'lat as b', 'orderNum as c')
                                   ->get()
                                   ->map(function ($item) {

                                       // 转成数字，节省引号d
                                       return [
                                           (float)$item->a,
                                           (float)$item->b,
                                           (int)$item->c,
                                       ];
                                   })->toJson();

        // 写入本地文件
        $path = sys_get_temp_dir() . "/{$province}_{$city}_{$sourceType}.json";
        file_put_contents($path, $data);

        // oss path
        $objectName = self::getCoordinateOssPath($province, $city, $sourceType, false, false);

        // oss上传
        $detailUrl = FileUtil::uploadFile($path, 'json', $objectName);

        dump($detailUrl);

        @unlink($path);
    }

    /**
     * 获取坐标点位对应的oss文件path
     * @param int $province
     * @param int $city
     * @param int $sourceType
     * @param bool $domain
     * @return string
     */
    public static function getCoordinateOssPath(int $province, int $city, int $sourceType, bool $domain = false, bool $query = true): string
    {
        $prefix = '';
        if ($domain) {
            $prefix = 'https://file.51kuafu.com/';
        }

        if (!$province && $city) {
            $province = AutoNaviProvinceCityArea::getInstance()->getProvinceByCityCode($city)['code'] ?? '';
        }

        if (!$province) {
            return '';
        }

        // 文件上次更新时间
        $lastTime = self::getOssTimeKey();

        // 区分环境
        $environment = in_array(config('app.env'), ['prod', 'pre']) ? 'prod' : 'test';

        $file = $prefix . "thermal_map/{$environment}/{$province}_{$city}_{$sourceType}.json";

        if ($query) {
            $file .= "?t={$lastTime}";
        }

        return $file;
    }

    /**
     * 上次更新坐标文件的时间
     */
    const GENERATE_COORDINATE_OSS_TIME = 'generate_coordinate_oss_time';

    public static function updateOssTimeKey(): void
    {
        Redis::set(self::GENERATE_COORDINATE_OSS_TIME, time());
    }

    /**
     * 获取上次更新坐标文件的时间
     * @return int
     */
    private static function getOssTimeKey(): int
    {
        return Redis::get(self::GENERATE_COORDINATE_OSS_TIME) ?: 0;
    }


    /**
     * 读取csv文件
     * @param string $url
     * @param \Closure $closure
     * @return void
     */
    private function readCsv(string $url, \Closure $closure)
    {
        $file = fopen($url, 'r');

        $row = 0;

        while (!feof($file)) {
            $line = fgetcsv($file);
            if ($line === false) {
                break;
            }

            $row++;

//            if ($row == 1) {
//                $start = microtime(true);
//            }
//
//            if ($row == 100000) {
//                $end = microtime(true);
//                dump($end - $start);
//                exit;
//            }

            if ($row == 1) {
                continue;
            }

            if ($row % self::INSERT_EACH_LIMIT == 0) {
                echo $row . PHP_EOL;
            }

            $closure($row, $line);
        }

        fclose($file);
    }
}
